package org.jflame.commons.excel;

import org.jflame.commons.convert.Converter;
import org.jflame.commons.excel.convertor.NullConverter;
import org.jflame.commons.reflect.BeanHelper;
import org.jflame.commons.util.ArrayHelper;
import org.jflame.commons.util.NumberHelper;
import org.jflame.commons.util.StringHelper;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Workbook;

/**
 * ExcelColumn注解属性封装类
 * 
 * @author charles.zhang
 */
@SuppressWarnings("rawtypes")
public class ExcelColumnProperty implements Comparable<ExcelColumnProperty> {

    private PropertyDescriptor propertyDescriptor;
    private String name;
    private int order;
    private int width;
    private String fmt;
    private Converter<?,String> writeConverter;
    private Converter readConverter;
    // private boolean nonSCINotation;
    private int columnType;
    private CellStyle columnStyle;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public String getFmt() {
        return fmt;
    }

    public void setFmt(String fmt) {
        this.fmt = fmt;
    }

    public PropertyDescriptor getPropertyDescriptor() {
        return propertyDescriptor;
    }

    public void setPropertyDescriptor(PropertyDescriptor propertyDescriptor) {
        this.propertyDescriptor = propertyDescriptor;
    }

    public Converter<?,String> getWriteConverter() {
        return writeConverter;
    }

    public void setWriteConverter(Converter<?,String> writeConverter) {
        this.writeConverter = writeConverter;
    }

    public Converter getReadConverter() {
        return readConverter;
    }

    public void setReadConverter(Converter readConverter) {
        this.readConverter = readConverter;
    }

    /*public boolean isNonSCINotation() {
        return nonSCINotation;
    }
    
    public void setNonSCINotation(boolean nonSCINotation) {
        this.nonSCINotation = nonSCINotation;
    }*/

    public int getColumnType() {
        return columnType;
    }

    public void setColumnType(int columnType) {
        this.columnType = columnType;
    }

    public CellStyle getColumnStyle() {
        return columnStyle;
    }

    public void setColumnStyle(CellStyle columnStyle) {
        this.columnStyle = columnStyle;
    }

    public int compareTo(ExcelColumnProperty obj) {
        return this.order - obj.order;
    }

    public static List<ExcelColumnProperty> resolveColumnProperty(final Class<?> dataClass, boolean isWrite,
            Optional<String> group, final Workbook workbook) {
        return resolveColumnProperty(dataClass, isWrite, group, false, workbook);
    }

    /**
     * 从bean属性解析出@ExcelColumn注解设置.
     * 
     * @param dataClass Class&lt;? extends IExcelEntity&gt;
     * @param isWrite 是否是写操作,即生成excel
     * @param group 属性分组
     * @param groupStrict true表示只包含指定分组的属性,false包括指定分组和没有任何分组的属性
     * @param workbook
     * @return excel column注解属性
     */
    public static List<ExcelColumnProperty> resolveColumnProperty(final Class<?> dataClass, boolean isWrite,
            Optional<String> group, boolean groupStrict, final Workbook workbook) {

        final Class<ExcelColumn> clazz = ExcelColumn.class;
        final String clazzName = "class";

        PropertyDescriptor[] properties = BeanHelper.getPropertyDescriptors(dataClass);
        List<ExcelColumnProperty> columnProperties = new ArrayList<ExcelColumnProperty>(properties.length);

        ExcelColumn tmpAnns;
        Field tmpField;
        Method tmpReadMethod;
        ExcelColumnProperty tmpProperty;
        for (PropertyDescriptor propDesc : properties) {
            if (clazzName.equals(propDesc.getName())) {
                continue;
            }
            tmpAnns = null;
            tmpReadMethod = propDesc.getReadMethod();
            if (tmpReadMethod != null && tmpReadMethod.isAnnotationPresent(clazz)) {
                tmpAnns = tmpReadMethod.getAnnotation(clazz);
            }
            if (tmpAnns == null) {
                tmpField = FieldUtils.getField(dataClass, propDesc.getName(), true);
                if (tmpField != null && tmpField.isAnnotationPresent(clazz)) {
                    tmpAnns = tmpField.getAnnotation(clazz);
                }
            }
            if (tmpAnns == null) {
                continue;
            }

            if (group.isPresent()) {
                // 分组严格模式,只包含指定分组的属性,宽松模式包括指定分组和没有任何分组的属性
                if (groupStrict) {
                    if (!ArrayUtils.contains(tmpAnns.group(), group.get())) {
                        continue;
                    }
                } else {
                    if (ArrayUtils.isNotEmpty(tmpAnns.group()) && !ArrayUtils.contains(tmpAnns.group(), group.get())) {
                        continue;
                    }
                }
            }

            tmpProperty = new ExcelColumnProperty();
            tmpProperty.setPropertyDescriptor(propDesc);
            tmpProperty.setOrder(tmpAnns.order());
            tmpProperty.setFmt(tmpAnns.fmt());
            tmpProperty.setName(tmpAnns.name());
            tmpProperty.setColumnType(tmpAnns.columnType());

            if (isWrite && workbook != null && StringHelper.isNotBlank(tmpAnns.style())) {
                CellCssStyle cellCss = CellCssStyle.parse(tmpAnns.style());
                if (NumberHelper.gtZero(cellCss.getWidth())) {
                    tmpProperty.setWidth(cellCss.getWidth());
                }
                tmpProperty.setColumnStyle(cellCss.toCellStyle(workbook));
            }

            setConverter(isWrite, tmpProperty, tmpAnns);

            columnProperties.add(tmpProperty);

        }
        Collections.sort(columnProperties);
        return columnProperties;
    }

    public static List<ExcelColumnProperty> resolveColumnProperty(final String[] titles, final List<Object[]> data) {
        int size = ArrayHelper.isNotEmpty(titles) ? titles.length : data.get(0).length;
        List<ExcelColumnProperty> columnProperties = new ArrayList<>(size);

        ExcelColumnProperty currentProperty;

        for (int cellIndex = 0; cellIndex < size; cellIndex++) {
            currentProperty = new ExcelColumnProperty();
            currentProperty.setOrder(cellIndex);
            currentProperty.setColumnType(0);
            if (ArrayHelper.isNotEmpty(titles)) {
                currentProperty.setName(titles[cellIndex]);
            }
            columnProperties.add(currentProperty);
        }
        return columnProperties;
    }

    @SuppressWarnings("unchecked")
    private static void setConverter(boolean isWrite, ExcelColumnProperty colProp, ExcelColumn tmpAnns) {
        if (isWrite) {
            Converter<?,String> converter = null;
            if (tmpAnns.writeConverter() != NullConverter.class) {
                if (StringHelper.isNotEmpty(tmpAnns.fmt())) {
                    try {
                        converter = tmpAnns.writeConverter()
                                .getConstructor(String.class)
                                .newInstance(tmpAnns.fmt());
                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                            | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                        try {
                            converter = ConstructorUtils.invokeConstructor(tmpAnns.writeConverter(),
                                    colProp.getPropertyDescriptor()
                                            .getPropertyType(),
                                    String.class);
                        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                                | InvocationTargetException | NoSuchMethodException | SecurityException e1) {
                            ExceptionUtils.rethrow(e1);
                        }
                    }
                } else {
                    try {
                        converter = tmpAnns.writeConverter()
                                .getConstructor()
                                .newInstance();
                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                            | SecurityException | InvocationTargetException | NoSuchMethodException e1) {
                        ExceptionUtils.rethrow(e1);
                    }
                }
                colProp.setWriteConverter(converter);
            }
        } else {
            if (tmpAnns.readConverter() != NullConverter.class) {
                Converter<?,?> converter = null;
                if (StringHelper.isNotEmpty(tmpAnns.fmt())) {
                    try {
                        converter = tmpAnns.readConverter()
                                .getConstructor(String.class)
                                .newInstance(tmpAnns.fmt());
                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                            | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                        ExceptionUtils.rethrow(e);
                    }
                } else {
                    try {
                        converter = tmpAnns.readConverter()
                                .getConstructor()
                                .newInstance();
                    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                            | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                        ExceptionUtils.rethrow(e);
                    }
                }
                colProp.setReadConverter(converter);
            }
        }
    }
}
